/**
*
* Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
**/
package lucee.runtime.crypt;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.StringUtil;
import lucee.runtime.coder.Coder;
import lucee.runtime.exp.PageException;
import lucee.runtime.op.Caster;
/**
*
*/
public class Cryptor {
public final static String DEFAULT_CHARSET = "UTF-8";
public final static String DEFAULT_ENCODING = "UU";
public final static int DEFAULT_ITERATIONS = 1000; // minimum recommended per NIST
private final static SecureRandom secureRandom = new SecureRandom();
/**
* @param input - the clear-text input to be encrypted, or the encrypted input to be decrypted
* @param key - the encryption key
* @param algorithm - algorithm in JCE scheme
* @param ivOrSalt - Initialization Vector for algorithms with Feedback Mode that is not ECB, or Salt for Password Based Encryption algorithms
* @param iterations - number of Iterations for Password Based Encryption algorithms (recommended minimum value is 1000)
* @param doDecrypt - the Operation Type, pass false for Encrypt or true for Decrypt
* @return
* @throws PageException
*/
static byte[] crypt( byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations, boolean doDecrypt ) throws PageException {
try {
return _crypt( input, key, algorithm, ivOrSalt, iterations, doDecrypt );
}
// this is an ugly patch but it looks lime that ACF simply double to short keys
catch(PageException pe){
String msg=pe.getMessage();
if(msg!=null && key.length()==4 && msg.indexOf(" 40 ")!=-1 && msg.indexOf(" 1024 ")!=-1) {
return _crypt( input, key+key, algorithm, ivOrSalt, iterations, doDecrypt );
}
if(msg!=null && key.length()>4 && key.length()%4==0 && msg.indexOf("Illegal key size")!=-1) {
return crypt( input, key.substring(0,key.length()-4), algorithm, ivOrSalt, iterations, doDecrypt );
}
throw pe;
}
}
private static byte[] _crypt( byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations, boolean doDecrypt ) throws PageException {
byte[] result = null;
Key secretKey = null;
AlgorithmParameterSpec params = null;
String algo = algorithm;
boolean isFBM = false, isPBE = StringUtil.startsWithIgnoreCase( algo, "PBE" );
int ivsLen = 0, algoDelimPos = algorithm.indexOf( '/' );
if ( algoDelimPos > -1 ) {
algo = algorithm.substring( 0, algoDelimPos );
isFBM = !StringUtil.startsWithIgnoreCase( algorithm.substring( algoDelimPos + 1 ), "ECB" );
}
try {
Cipher cipher = Cipher.getInstance( algorithm );
if ( ivOrSalt == null ) {
if ( isPBE || isFBM ) {
ivsLen = cipher.getBlockSize();
ivOrSalt = new byte[ ivsLen ];
if ( doDecrypt )
System.arraycopy( input, 0, ivOrSalt, 0, ivsLen );
else
secureRandom.nextBytes( ivOrSalt );
}
}
if ( isPBE ) {
secretKey = SecretKeyFactory.getInstance( algorithm ).generateSecret( new PBEKeySpec( key.toCharArray() ) );
params = new PBEParameterSpec( ivOrSalt, iterations > 0 ? iterations : DEFAULT_ITERATIONS ); // set Salt and Iterations for PasswordBasedEncryption
}
else {
secretKey = new SecretKeySpec( Coder.decode( Coder.ENCODING_BASE64, key ), algo );
if ( isFBM )
params = new IvParameterSpec( ivOrSalt ); // set Initialization Vector for non-ECB Feedback Mode
}
if ( doDecrypt ) {
cipher.init( Cipher.DECRYPT_MODE, secretKey, params );
result = cipher.doFinal( input, ivsLen, input.length - ivsLen );
}
else {
cipher.init( Cipher.ENCRYPT_MODE, secretKey, params );
result = new byte[ ivsLen + cipher.getOutputSize( input.length ) ];
if ( ivsLen > 0 )
System.arraycopy( ivOrSalt, 0, result, 0, ivsLen );
cipher.doFinal( input, 0, input.length, result, ivsLen );
}
return result;
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw Caster.toPageException( t );
}
}
/**
* an encrypt method that takes a byte-array for input and returns an encrypted byte-array
*/
public static byte[] encrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException {
return crypt( input, key, algorithm, ivOrSalt, iterations, false );
}
/**
* an encrypt method that takes a clear-text String for input and returns an encrypted, encoded, String
*/
public static String encrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException {
try {
if ( charset == null ) charset = DEFAULT_CHARSET;
if ( encoding == null ) encoding = DEFAULT_ENCODING;
byte[] baInput = input.getBytes( charset );
byte[] encrypted = encrypt( baInput, key, algorithm, ivOrSalt, iterations );
return Coder.encode( encoding, encrypted );
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw Caster.toPageException( t );
}
}
/**
* a decrypt method that takes an encrypted byte-array for input and returns an unencrypted byte-array
*/
public static byte[] decrypt(byte[] input, String key, String algorithm, byte[] ivOrSalt, int iterations) throws PageException {
return crypt( input, key, algorithm, ivOrSalt, iterations, true );
}
/**
* a decrypt method that takes an encrypted, encoded, String for input and returns a clear-text String
*/
public static String decrypt(String input, String key, String algorithm, byte[] ivOrSalt, int iterations, String encoding, String charset) throws PageException {
try {
if ( charset == null ) charset = DEFAULT_CHARSET;
if ( encoding == null ) encoding = DEFAULT_ENCODING;
byte[] baInput = Coder.decode( encoding, input );
byte[] decrypted = decrypt( baInput, key, algorithm, ivOrSalt, iterations );
return new String( decrypted, charset );
}
catch(Throwable t) {
ExceptionUtil.rethrowIfNecessary(t);
throw Caster.toPageException( t );
}
}
}